# python-resmon - Python tools for Resmon monitoring and the Circonus API
# Copyright (c) 2010 Crown coypright
# 
# This file is part of python-resmon.
# 
# python-resmon is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# python-resmon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with python-resmon.  If not, see <http://www.gnu.org/licenses/>.

"""Set up a queue of alerts received from Circonus' API"""

import sys
import re
import threading
import time

from checker.alert import *

# Compatibility stub classes so that the alert classes can work
# with the limited data returned from circonus

class CirconusHost(object):
    def __init__(self, hostname):
        self.hostname = hostname


class CirconusMetric(object):
    def __init__(self, name):
        self.name = name

    def get_absence_severity(self):
        # This is just used as a default in the alert
        # The feed overrides this value.
        return 1

    def get_human_name(self):
        return self.name

    def get_format(self):
        return '%s'


class CirconusAlertFeed(object):

    POLL_INTERVAL = 20 #seconds

    def __init__(self, api, rules):
        self.api = api
        self.keeprunning = True 
        self.lastpoll = time.time()

        self.callbacks = set()
        
        self.build_rulemap(rules)

    def build_rulemap(self, rules):
        """Build lookup tables of host/metric so that we can access metric
        information as alerts come in."""

        rulemap = {}
        for node in rules:
            for m in node.get_metrics():
                rulemap[(node.hostname, m.name)] = (node, m)
        self.rulemap = rulemap

    def add_alert_listener(self, l):
        self.callbacks.add(l)

    def remove_alert_listener(self, l):
        self.callbacks.remove(l)

    def dispatch(self, alert):
        for c in self.callbacks:
            c(alert)

    def poll(self):
        alerts = self.api.list_alerts(start=self.lastpoll)
        self.lastpoll = int(time.time())
        for a in alerts:
            hostname = re.sub(r'\s+\(resmon:.*?\)', '', a.check_name)
            self.dispatch(self.get_alert(a.severity, hostname, a.metric_name, a.value, a.has_cleared()))

    def run(self):
        """Start polling."""
        self.keeprunning = True
        while self.keeprunning:
            try:
                self.poll()
            except IOError, e:
                print >>sys.stderr, e
            time.sleep(self.POLL_INTERVAL)

    def cast_value(self, value):
        if re.match(r'^\d+\.\d+', value):
            value = float(value)
        elif re.match(r'\d+', value):
            value = int(value)
        return value

    def get_alert(self, severity, hostname, metricname, value, cleared):
        """Cast a Circonus alert to an appropriate Alert class."""
        try:
            node, metric = self.rulemap[(hostname, metricname)]
        except KeyError:
            node = CirconusHost(hostname)
            metric = CirconusMetric(metricname)

        if value == 'null':
            if cleared:
                m = MetricRestored(node, metric)
            else:
                m = AbsentAlert(node, metric)
            m.severity = severity
            return m
        else:
            if cleared:
                return AlertCleared(severity, node, metric)
            else:
                return RuleAlert(severity, node, metric, self.cast_value(value))
